//
// Created by 郝羽 on 2017/11/16.
//

#ifndef RTC_XSWITCH2_RTP_EXTEN_HPP
#define RTC_XSWITCH2_RTP_EXTEN_HPP

#include <cstdint>
#include <cstring>
#include <functional>
#include <map>
#include <ostream>
#include <vector>

#include "xcutil.h"

enum RtpExtensionType {
    Unknown,
    AbsSendTime,
    TransportSequenceNumber,
    SSRCAudioLevel,
    CSRCAudioLevel,
};

class RtpExtensionMap {
private:
    RtpExtensionMap() {};
public:
    void add(const char* uri, int id) {
        for (auto it : RtpExtensionMap::uris()) {
            if (strcasecmp(it.second, uri) == 0) {
                _exten_type_to_id.emplace(it.first, id);
                _exten_id_to_type.emplace(id, it.first);
                return;
            }
        }
        return;
    };

    void add(RtpExtensionType type, int id) {
        if (type == RtpExtensionType::Unknown) {
            return;
        }
        _exten_type_to_id.emplace(type, id);
        _exten_id_to_type.emplace(id, type);
    };

    int getId(RtpExtensionType type) const {
        auto it = _exten_type_to_id.find(type);
        if (it == _exten_type_to_id.end()) {
            return 0;
        }
        return it->second;
    };

    RtpExtensionType getType(int id) const {
        auto it = _exten_id_to_type.find(id);
        if (it == _exten_id_to_type.end()) {
            return RtpExtensionType::Unknown;
        }
        return it->second;
    };

    void for_each(std::function<void(RtpExtensionType, int)> cb) {
        auto copy = _exten_type_to_id;
        for (auto it : copy) {
            cb(it.first, it.second);
        }
    };

public:
    static std::map<RtpExtensionType, const char*>& uris() {
        static std::map<RtpExtensionType, const char*> u = {
                {RtpExtensionType::AbsSendTime,                   "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"},
                {RtpExtensionType::TransportSequenceNumber,       "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"},
                {RtpExtensionType::SSRCAudioLevel,                "urn:ietf:params:rtp-hdrext:ssrc-audio-level"},
                {RtpExtensionType::CSRCAudioLevel,                "urn:ietf:params:rtp-hdrext:csrc-audio-level"}
        };
        return u;
    };

    static const char* uri(RtpExtensionType type) {
        auto it = uris().find(type);
        if (it == uris().end()) {
            return "unknown-rtp-header-extension-uri";
        }
        return it->second;
    }

    static RtpExtensionMap *create() {
        return new RtpExtensionMap();
    };

    static void destroy(RtpExtensionMap *rem) {
        delete rem;
    };

    static RtpExtensionMap *negotiate_audio(RtpExtensionMap *r) {
        auto l = new RtpExtensionMap();
        return l;
    };

    static RtpExtensionMap *negotiate_video(RtpExtensionMap *r) {
        auto l = new RtpExtensionMap();
        if (r == nullptr) {
            return l;
        }
        r->for_each([&l](RtpExtensionType type, int id) {
            switch (type) {
                case RtpExtensionType::TransportSequenceNumber:
                case RtpExtensionType::AbsSendTime:
                case RtpExtensionType::SSRCAudioLevel:
                case RtpExtensionType::CSRCAudioLevel:
                    l->add(type, id);
                    break;
                default:
                break;
            }
        });
        return l;
    };

    static RtpExtensionMap *default_audio() {
        auto m = new RtpExtensionMap();
        m->add(RtpExtensionType::SSRCAudioLevel, 1);
        m->add(RtpExtensionType::CSRCAudioLevel, 2);
        return m;
    };

    static RtpExtensionMap *default_video() {
        auto m = new RtpExtensionMap();
        m->add(RtpExtensionType::AbsSendTime, 1);
        m->add(RtpExtensionType::TransportSequenceNumber, 2);
        return m;
    };
private:
    std::map<RtpExtensionType, int> _exten_type_to_id;
    std::map<int, RtpExtensionType> _exten_id_to_type;
};

class RtpExtensions {
public:
    uint32_t abs_send_time;
    bool has_abs_send_time;
    uint16_t transport_seq_num;
    bool has_transport_seq_num;
    uint8_t ssrc_audio_level;
    bool has_ssrc_audio_level;
    std::vector<uint8_t> csrc_audio_levels;
    bool has_csrc_audio_levels;

    size_t read(uint8_t* buf, size_t len, RtpExtensionMap* m) {
        if (m == nullptr) {
            return 0;
        }
        uint16_t ext_len = be_get_u16(buf + 2);
        if (len < ext_len) {
            return 0;
        }
        uint8_t* ext_end = buf + 4 * ext_len;
        uint8_t* ext_ptr = buf + 4;
        if (buf[0] == 0xBE && buf[1] == 0xDE) {
            while(ext_ptr < ext_end) {
                if (ext_ptr[0] != 0) {
                    uint8_t id = ext_ptr[0] >> 4;
                    if (id == 15) {
                        return ext_end - buf;
                    }
                    uint8_t l = ext_ptr[0] & 0x0F;
                    ext_ptr += 1;
                    auto type = m->getType(id);
                    switch(type) {
                        case RtpExtensionType::AbsSendTime:
                            //  0                   1                   2                   3
                            //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
                            // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                            // |  ID   | len=2 |              absolute send time               |
                            // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                            if (l != 2) {
                                return 0;
                            }
                            abs_send_time = be_get_u24(ext_ptr);
                            has_abs_send_time = true;
                            ext_ptr += 3;
                            break;
                        case RtpExtensionType::TransportSequenceNumber:
                            //   0                   1                   2
                            //   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
                            //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                            //  |  ID   | L=1   |transport wide sequence number |
                            //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                            if (l != 1) {
                                return 0;
                            }
                            transport_seq_num = be_get_u16(ext_ptr);
                            has_transport_seq_num = true;
                            ext_ptr += 2;
                            break;
                        case RtpExtensionType::SSRCAudioLevel:
                            //   0                   1
                            //   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
                            //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                            //   |  ID   | len=0 |V| level       |
                            //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                            if (l != 0) {
                                return 0;
                            }
                            ssrc_audio_level = *ext_ptr & 0x7F;
                            has_ssrc_audio_level = true;
                            ext_ptr += 1;
                            break;
                        case RtpExtensionType::CSRCAudioLevel:
                            csrc_audio_levels.clear();
                            csrc_audio_levels.emplace_back(*ext_ptr & 0x7F);
                            ext_ptr += 1;
                            while (l--) {
                                csrc_audio_levels.emplace_back(*ext_ptr & 0x7F);
                                ext_ptr += 1;
                            }
                            has_csrc_audio_levels = true;
                            break;
                        default:
                        break;
                    }
                } else {
                    ext_ptr += 1;
                }
            }
            return ext_end - buf;
        } else {
            // two byte rtp header extension not used for now
            return 0;
        }
    };

    size_t write(uint8_t* buf, size_t len, RtpExtensionMap* m) {
        if (!has_abs_send_time && !has_transport_seq_num) {
            return 0;
        }
        uint8_t abs_send_time_id = m->getId(RtpExtensionType::AbsSendTime);
        uint8_t transport_seq_num_id = m->getId(RtpExtensionType::TransportSequenceNumber);
        uint8_t ssrc_audio_level_id = m->getId(RtpExtensionType::SSRCAudioLevel);
        uint8_t csrc_audio_level_id = m->getId(RtpExtensionType::CSRCAudioLevel);
        size_t ext_len = (has_abs_send_time && abs_send_time_id != 0 ? 4 : 0) +
                         (has_transport_seq_num && transport_seq_num_id != 0 ? 3 : 0) +
                         (has_ssrc_audio_level ? 2 : 0) +
                         (has_csrc_audio_levels ? csrc_audio_levels.size() + 1 : 0);

        ext_len = (ext_len + 3) / 4;
        if (len < 4 + 4 * ext_len) {
            return 0;
        }
        size_t offset = 0;
        buf[offset++] = 0xBE;
        buf[offset++] = 0xDE;
        be_set_u16(ext_len, buf + offset);
        offset += 2;
        memset(buf + offset, 0, 4 * ext_len);
        if (has_abs_send_time && abs_send_time_id) {
            buf[offset++] = abs_send_time_id << 4 | 2;
            be_set_u24(abs_send_time, buf + offset);
            offset += 3;
        }
        if (has_transport_seq_num && transport_seq_num_id) {
            buf[offset++] = transport_seq_num_id << 4 | 1;
            be_set_u16(transport_seq_num, buf + offset);
            offset += 2;
        }
        if (has_ssrc_audio_level) {
            buf[offset++] = ssrc_audio_level_id << 4 | 0;
            buf[offset++] = ssrc_audio_level;
        }
        if (has_csrc_audio_levels) {
            buf[offset++] = csrc_audio_level_id << 4 | (csrc_audio_levels.size() - 1);
            for (auto al : csrc_audio_levels) {
                buf[offset++] = al;
            }
        }
        while (offset % 4) {
            buf[offset++] = 0;
        }
        return offset;
    };
};

#endif //RTC_XSWITCH2_RTP_EXTEN_HPP
